package com.i72.basic.init;

import com.i72.basic.context.ThreadContext;
import com.i72.basic.tools.BeanHelper;
import com.i72.basic.dynamic.JavassistCode;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

import java.util.*;

public class AOPRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {


    private BeanFactory factory = null;

    //private Boolean around = false;
    @Override
    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)  {



        Set<String> attributeNames = importingClassMetadata.getAnnotationTypes();

        //List<Class> classList = new ArrayList<>();
        for(String name:attributeNames){
            if(name.contains("AOPPattern") || name.contains("AOPAnnotation")|| name.contains("AOPClassName")) {
                //根据注解
                Map m = importingClassMetadata.getAllAnnotationAttributes(name);

                ThreadContext.add("aop",m);

                /*
                List list = (List)m.get("target");

                Class c = (Class)list.get(0);

                List list2 = (List)m.get("invoke");

                Class c2 = (Class)list2.get(0);
                Class cc = null;

                Boolean around = (Boolean)((List)m.get("around")).get(0);
                */

                //this.around = around;
                /*
                try {
                    if (c.isAnnotation()) {
                        //按注解扫描

                        //cc = buildAOPAspectForAnnotation(c, c2,around);


                    } else {

                        if (c.toGenericString().contains("class")) {
                            //按类拦截，默认拦截该类的所有公开方法
                            //cc = buildAOPAspectForClass(c, c2,around);
                        }

                    }

                    if(cc!=null){
                        classList.add(cc);
                    }

                }catch (Exception e){

                }*/



            }
        }

        //BeanHelper.registerBean(factory,false,classList);


    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.factory = beanFactory;
    }


    /**
     * 动态构建拦截注解的AOP切面类
     */
    private Class buildAOPAspectForAnnotation(Class annotation,Class invokeClass,Boolean around) throws Exception{

        String expression = "@target(" + annotation.getName() + ")";

        return createAspectClass(expression,invokeClass,around);

    }

    /**
     * 动态构建拦截类的AOP切面类
     */
    private Class buildAOPAspectForClass(Class classType,Class invokeClass,Boolean around) throws Exception{

        String expression = "within(" + classType.getName() + ")";
        return createAspectClass(expression,invokeClass,around);
    }


    //动态构建切面类
    private Class createAspectClass(String pointExpression,Class invokeClass,Boolean around) throws Exception{

        AOPInvoke notice = (AOPInvoke)factory.getBean(invokeClass.getSimpleName());

        if(notice!=null && notice.getClass().getClass().isAssignableFrom(AOPInvoke.class)){
            throw new Exception("不存在BEAN，或BEAN 没有实现AOPInvoke");
        }

        String packageName = "com.i72.basic.dynamic.aop";

        String AspectName = packageName+".Aspect"+new Long(System.currentTimeMillis()*100).toString().replace(".","");


        JavassistCode javassistCode = JavassistCode.createClass(AspectName);


        try {
            //添加类注解


            javassistCode.addAnnotation("org.aspectj.lang.annotation.Aspect");

            Map m = new HashMap();
            //因为当前类被动态注册到spring中了，所以不需要在声明为Component，以免多次注册
            //javassistCode.addAnnotation("org.springframework.stereotype.Component");
            javassistCode.buildAnnotation();


                        //注入通知字段，是一个自定义实现类
            javassistCode.addField("notice","private "+invokeClass.getName()+" notice;").addFieldAnnotation("org.springframework.beans.factory.annotation.Autowired").buildFieldAnnotation();

            //添加方法
            //添加切点方法
            m.put("value", pointExpression);
            javassistCode.addMethod("point", "public void  point(){}").addMethodAnnotation("org.aspectj.lang.annotation.Pointcut", m).buildMethodAnnotation();

            m.clear();
            m.put("value", "point()");
            javassistCode.addMethod("before", "public void before(org.aspectj.lang.JoinPoint joinPoint){notice.invoke(\"before\",joinPoint);}").addMethodAnnotation("org.aspectj.lang.annotation.Before", m).buildMethodAnnotation();

            javassistCode.addMethod("after", "public void after(org.aspectj.lang.JoinPoint joinPoint){notice.invoke(\"after\",joinPoint);}").addMethodAnnotation("org.aspectj.lang.annotation.After", m).buildMethodAnnotation();

            javassistCode.addMethod("afterReturn", "public void afterReturn(org.aspectj.lang.JoinPoint joinPoint){notice.invoke(\"afterReturn\",joinPoint);}").addMethodAnnotation("org.aspectj.lang.annotation.AfterReturning", m).buildMethodAnnotation();

            if(around) {
                //根据需要加入AOP Around
                //因为Around 和 其他通知类型是互斥的，详见Spring AOP使用规则
                javassistCode.addMethod("around", "public void around(org.aspectj.lang.ProceedingJoinPoint proceedingJoinPoint){notice.invoke(\"around\",proceedingJoinPoint);}").addMethodAnnotation("org.aspectj.lang.annotation.Around", m).buildMethodAnnotation();
            }
            m.put("throwing","ex");
            javassistCode.addMethod("afterThrowing", "public void afterThrowing(Throwable ex){notice.invoke(\"afterThrowing\",ex);}").addMethodAnnotation("org.aspectj.lang.annotation.AfterThrowing", m).buildMethodAnnotation();



            Class c = javassistCode.toClass();

            return c;

        }catch (Exception e){

        }

        return  null;

    }
}
